/*
 * class Parser
 * 
 * Parses the tokens returned from the lexical analyzer.
 * 
 * Update the parser implementation to parse the Decaf
 * language according to the grammar provided. 
 */
public class Parser {

	private Lexer lexer; // lexical analyzer
	private Token token; // look ahead token
	
	public Parser(Lexer lex) {
		lexer = lex;
	}
	
	public boolean parse() {
		
		token = lexer.nextToken();
		
		boolean finalResult;
		
		if(token.getTokenType() == Token.EOF)
		{
			return false;
		}
		
		while(token.getTokenType() != Token.EOF) {
			finalResult = match(Token.KW, "class");
			finalResult &= match(Token.ID);
			finalResult &= match(Token.LB);
			finalResult &= methodDecls();
			finalResult &= match(Token.RB);
			
			if(!finalResult)
				return false;
		}
		
		return true;
	}
	
	private boolean methodDecls()
	{
		return methodDeclsHelper();
	}
	
	private boolean methodDeclsHelper()
	{
		boolean value = true;
		
		if(token.getTokenType() == Token.KW && token.getLexeme().equals("static"))
		{
			value = methodDecl() & methodDeclsHelper();
		}
		
		return value;
		
	}
	
	private boolean methodDecl()
	{
		boolean value;
		value  =  match(Token.KW, "static");
		value &=  type();
		value &=  match(Token.ID);
		value &=  match(Token.LP);
		
		value &=  formalParams();
		
		value &=  match(Token.RP);
		
		value &= block();
		
		return value;
	}
	
	private boolean type()
	{
		if(match(Token.KW,"int"))
			return true;
		if(match(Token.KW,"float"))
			return true;
		if(match(Token.KW,"String"))
			return true;
		if(match(Token.KW,"boolean"))
			return true;
		
		return false;
	}
	
	private boolean formalParams()
	{
		boolean value = true;
		if(token.getTokenType() == Token.KW)
		{
			value = properFormalParams();
		}
		return value;
		
	}
	
	private boolean properFormalParams()
	{
		boolean value = formalParam() & properFormalParamsHelper();
		return value;
	}
	
	private boolean properFormalParamsHelper()
	{
		boolean value = true;
	
		if(token.getTokenType() == Token.FA)
		{
			value = match(Token.FA) & formalParam() & properFormalParamsHelper();
		}
		return value;
	}
	
	private boolean formalParam()
	{
		boolean value = type();
		value &= match(Token.ID);
		
		return value;
	}
	
	private boolean block()
	{
		boolean value = match(Token.LB);
		value &= statements();
		value &= match(Token.RB);
		
		return value;
	}
	
	private boolean statements()
	{
		return statementsHelper();
	}
	
	private boolean statementsHelper()
	{
		boolean value = true;
		
		if(token.getTokenType() == Token.LB)
		{
			value = statement() & statementsHelper();
		}
		else 
		{
			if(token.getTokenType() == Token.KW)
			{
				
				if(isType(token.getLexeme())||
				   token.getLexeme().equals("if") ||
				   token.getLexeme().equals("while") ||
				   token.getLexeme().equals("return"))
				{
					
					value = statement() & statementsHelper();
				}
			}
			else
			{
				if(token.getTokenType()==Token.ID)
					value = statement() & statementsHelper();
				
				
			}
		}
		
		return value;
	}
	
	private boolean statement()
	{
		if(token.getTokenType() == Token.LB)
			return block();
		
	
		if(token.getTokenType()==Token.ID)
			return assignStmt();
		
		
		
		if(token.getTokenType() == Token.KW)
		{
			
			
			if(token.getLexeme().equals("if"))
			{
				return ifStmt();
			}
			
			if(token.getLexeme().equals("while"))
			{
				return whileStmt();
			}
			if(token.getLexeme().equals("return"))
			{
				
				return returnStmt();
			}
			if(isType(token.getLexeme()))
			{
				return localVarDecl();
			}
		}
		
		return false;
		
	}
	
	private boolean localVarDecl() 
	{
		boolean value = type() & match(Token.ID) & match(Token.SM);
		return value;
	}
	
	private boolean assignStmt() 
	{
		boolean value = match(Token.ID) & match(Token.AO) & expression() & match(Token.SM);
		return value;
	}

	private boolean ifStmt() {
		
		boolean value = match(Token.KW,"if");
		value &= match(Token.LP);
		value &= expression();
		value &= match(Token.RP);
		value &= statement();
		if(token.getTokenType()== Token.KW && token.getLexeme().equals("else"))
		{
			value&= match(Token.KW,"else");
			value&= statement();
		}
		
		return value;
	}

	private boolean whileStmt() {

		boolean value = match(Token.KW,"while");
		value &= match(Token.LP);
		value &= expression();
		value &= match(Token.RP);
		value&= statement();
		return value;
	}

	private boolean returnStmt()
	{
		
		boolean value = match(Token.KW,"return");
		value &= expression();
		value &= match(Token.SM);
		
		return value;
	}
	
	private boolean expression() 
	{
		boolean value = andExpr();
		if(token.getTokenType() == Token.LO)
		{
			value &= match(Token.LO);
			return value &= expression();
		}
		return value;
	}
	
	private boolean andExpr()
	{
		boolean value = eqExpr();
		
		if(token.getTokenType()==Token.LA)
		{
			value &= match(Token.LA);
			return value & andExpr();
		}
		return value;
	}
	
	private boolean eqExpr()
	{
		boolean value = addExpr();
		if(token.getTokenType() == Token.EQ)
		{
			value &= match(Token.EQ);
			return value &= eqExpr();
		}
		if(token.getTokenType() == Token.NE)
		{
			value &= match(Token.NE);
			return value &= eqExpr();
		}
		return value;
		
	}
	
	private boolean addExpr()
	{
		boolean value = multExpr();
		if(token.getTokenType() == Token.PO)
		{
			value &= match(Token.PO);
			return value &= addExpr();
		}
		if(token.getTokenType() == Token.MO)
		{
			value &= match(Token.MO);
			return value &= addExpr();
		}
		return value;
		
	}
	
	private boolean multExpr()
	{
		boolean value = primaryExpr();
		if(token.getTokenType() == Token.MD)
		{
			value &= match(Token.MD);
			return value &= multExpr();
		}
		if(token.getTokenType() == Token.DO)
		{
			value &= match(Token.DO);
			return value &= multExpr();
		}
		if(token.getTokenType() == Token.TO)
		{
			value &= match(Token.TO);
			return value &= multExpr();
		}
		return value;
	}
	
	private boolean primaryExpr()
	{
		if(match(Token.NM))
			return true;
		
		if(match(Token.BL))
			return true;
		
		if(match(Token.ST))
			return true;
		
		if(token.getTokenType()==Token.ID)
		{
			boolean value = match(Token.ID);
			if(token.getTokenType()==Token.LP)
			{
				value &= match(Token.LP);
				
				if(token.getTokenType()!=Token.RP)
				{
					value &= properActualParams();
				}
				value &= match(Token.RP);
				
			}
			return value;
		}
		
		if(token.getTokenType() == Token.LP)
		{
			boolean val = match(Token.LP);
			val&= expression();
			val&= match(Token.RP);
			
			return val;
		}
		
		return false;
	}

	private boolean properActualParams() 
	{
		boolean value = expression();
		value &= properActualParamsHelper(); 
		return value;
	}

	private boolean properActualParamsHelper() 
	{
		boolean value = true;
		if(token.getTokenType()==Token.FA)
		{
			value &= match(Token.FA);
			value &= expression();
			value &= properActualParamsHelper();
		}
		return value;
	}

	private boolean isType(String s)
	{
		return (s.equals("int") || s.equals("float") ||
				s.equals("boolean")|| s.equals("String"));
	}
	

	private boolean match(int t) {
		System.out.println(token.getTokenType());
		if (token.getTokenType() == t) {
			
			token = lexer.nextToken();
			return true;
		} else {
			return false;
		}
	}
	
	private boolean match(int t, String s) {
		if (token.getTokenType() == t && token.getLexeme().equals(s)) {
			System.out.println(token.getLexeme());
			token = lexer.nextToken();
			return true;
		} else {
			return false;
		}
	}
}
